/*
 * Copyright 2008 Max Kugland
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.splink.library.logging {
	import org.splink.library.logging.logoutput.ILogOutput;
	import org.splink.library.utils.ArrayUtils;

	import flash.utils.clearInterval;
	import flash.utils.getQualifiedClassName;
	import flash.utils.setInterval;

	/**
	 * Basic <code>ILogger</code> implementation.
	 * 
	 * @author Max Kugland
	 */
	public class Logger implements ILogger {
		/**
		 * static to ensure the correct order is kept.
		 */
		private static var _logstack : Array = new Array();
		private var _logOutputs : Array = new Array();
		private var _range : LogRange = new LogRange(LogLevel.TRACE, LogLevel.GARBAGE);
		private var _className : String;
		private var _basePackage : String = "";
		private var _id : String;
		private var _initialized : Boolean = false;
		private var _intervalId : uint;

		public function Logger(logOutputs : Array, clazz : Class, id : String) {
			_id = id;
			_logOutputs = logOutputs;
			_className = getQualifiedClassName(clazz).replace("::", ".");

			_intervalId = setInterval(function() : void {
				if (outputsInitialized()) {
					clearInterval(_intervalId);
					flushStack();
				}
			}, 100);
		}

		/**
		 * @inheritDoc
		 */
		public function set range(range : LogRange) : void {
			_range = range;
		}

		/**
		 * @inheritDoc
		 */
		public function set basePackage(pack : String) : void {
			_basePackage = pack;
		}

		private function isBelowBasePackage() : Boolean {
			return _className.indexOf(_basePackage) != -1 || _basePackage == "" || _basePackage == null;
		}

		private function outputsInitialized() : Boolean {
			if (_initialized) return _initialized;
			for each (var output : ILogOutput in _logOutputs) {
				if (!output.getInitialized()) {
					return false;
				}
			}
			return _initialized = true;
		}

		/**
		 * @inheritDoc
		 */
		public function getLogOutput(clazz : Class) : ILogOutput {
			for each (var output : ILogOutput in _logOutputs) {
				if (output is clazz) {
					return output;
				}
			}
			return null;
		}

		/**
		 * @inheritDoc
		 */
		public function trace(...args) : void {
			log(LogLevel.TRACE, args.join(","));
		}

		/**
		 * @inheritDoc
		 */
		public function info(...args) : void {
			log(LogLevel.INFO, args.join(","));
		}

		/**
		 * @inheritDoc
		 */
		public function debug(...args) : void {
			log(LogLevel.DEBUG, args.join(","));
		}

		/**
		 * @inheritDoc
		 */
		public function warn(...args) : void {
			log(LogLevel.WARN, args.join(","));
		}

		/**
		 * @inheritDoc
		 */
		public function error(...args) : void {
			log(LogLevel.ERROR, args.join(","));
		}

		/**
		 * @inheritDoc
		 */
		public function fatal(...args) : void {
			log(LogLevel.FATAL, args.join(","));
		}

		/**
		 * @inheritDoc
		 */
		public function log(level : uint, message : *, ignoreRange : Boolean = false) : void {
			if (!isBelowBasePackage()) return;
			if (!ignoreRange) {
				if (level < _range.getLow() || level > _range.getHigh()) {
					return;
				}
			}

			var event : LogEvent = new LogEvent(getStackTrace(), level, message);
			if (outputsInitialized()) {
				if (_logstack.length > 0) flushStack();
				logOutput(event);
			} else {
				_logstack.push(new LogStackElement(_id, event));
			}
		}

		private function getStackTrace() : StackTrace {
			var stacktrace : StackTrace = StackParser.getStackTrace(_className, 4);
			if (stacktrace.clazz == "org.splink.library.logging.Logger") {
				stacktrace = StackParser.getStackTrace(_className, 5);
			}
			return stacktrace;
		}

		/**
		 * @inheritDoc
		 */
		public function logGarbageCollection(obj : *, id : String = null) : void {
			if (id == null) id = getQualifiedClassName(obj);
			var watchDog : GarbageWatchDog = new GarbageWatchDog(obj, id);
			watchDog.register(GarbageWatchDogEvent.COLLECTED, onGarbageCollected);
		}

		private function onGarbageCollected(event : GarbageWatchDogEvent) : void {
			event.source.unregister(GarbageWatchDogEvent.COLLECTED, onGarbageCollected);
			log(LogLevel.GARBAGE, event.getId().replace("::", "."), true);
		}

		private function logOutput(event : LogEvent) : void {
			for each (var output : ILogOutput in _logOutputs) {
				output.log(event);
			}
		}

		private function flushStack() : void {
			var stack : Array = _logstack.concat();
			for each (var elem : LogStackElement in stack) {
				if (elem.id == _id) {
					logOutput(elem.event);
					ArrayUtils.remove(_logstack, elem);
				}
			}
		}

		/**
		 * @inheritDoc
		 */
		public function finalize() : void {
			clearInterval(_intervalId);
			_logstack = new Array();
			_logOutputs = new Array();
			_range = null;
		}
	}
}
import org.splink.library.logging.LogEvent;

internal class LogStackElement {
	public var id : String;
	public var event : LogEvent;
	public var timestamp : Number;

	public function LogStackElement(id : String, event : LogEvent) {
		this.id = id;
		this.event = event;
		this.timestamp = event.getTimestamp();
	}
}